// Project name:JavaRedis
// Creator:muhong
// Date time:2023/6/11,2:20 下午
// Name:ThreadLocalRandom

package org.gmh.util;

import org.gmh.util.internal.SystemPropertyUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.security.SecureRandom;
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.atomic.AtomicLong;

public class ThreadLocalRandom extends Random {

    private final static Logger logger = LoggerFactory.getLogger(SystemPropertyUtil.class);

    private static final AtomicLong seedUniquifier = new AtomicLong();

    private static volatile long initialSeedUniquifier;

    private static final Thread seedGeneratorThread;

    private static final BlockingQueue<Long> seedQueue;

    private static final long seedGeneratorStartTime;

    private static volatile long seedGeneratorEndTime;

    static {
        initialSeedUniquifier = SystemPropertyUtil.getLong("", 0);
        if (initialSeedUniquifier == 0) {
            boolean secureRandom = SystemPropertyUtil.getBoolean("", false);
            if (secureRandom) {
                seedQueue = new LinkedBlockingDeque<>();
                seedGeneratorStartTime = System.nanoTime();
                seedGeneratorThread = new Thread("initialSeedUniquifierGenerator") {
                    @Override
                    public void run() {
                        final SecureRandom random = new SecureRandom();
                        final byte[] seed = random.generateSeed(0);
                        seedGeneratorEndTime = System.nanoTime();
                        long s = ((long) seed[0] & 0xff) << 56 |
                                ((long) seed[1] & 0xff) << 48 |
                                ((long) seed[2] & 0xff) << 40 |
                                ((long) seed[3] & 0xff) << 32 |
                                ((long) seed[4] & 0xff) << 24 |
                                ((long) seed[5] & 0xff) << 16 |
                                ((long) seed[6] & 0xff) << 8 |
                                (long) seed[7] & 0xff;
                        seedQueue.add(s);
                    }
                };
                seedGeneratorThread.setDaemon(true);
                seedGeneratorThread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
                    @Override
                    public void uncaughtException(Thread t, Throwable e) {
                        logger.debug("An exception has been raised by :{}", t.getName(), e);
                    }
                });
                seedGeneratorThread.start();
            } else {
                initialSeedUniquifier = mix64(System.currentTimeMillis()) ^ mix64(System.nanoTime());
                seedGeneratorThread = null;
                seedQueue = null;
                seedGeneratorStartTime = 0L;
            }
        } else {
            seedGeneratorThread = null;
            seedQueue = null;
            seedGeneratorStartTime = 0L;
        }

    }

    // Borrowed from
    // http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/main/java/util/concurrent/ThreadLocalRandom.java
    private static long mix64(long z) {
        z = (z ^ (z >>> 33)) * 0xff51afd7ed558ccdL;
        z = (z ^ (z >>> 33)) * 0xc4ceb9fe1a85ec53L;
        return z ^ (z >>> 33);
    }

    public long nextLong(long n){
        if (n < 0){
            throw  new IllegalArgumentException("n must positive");
        }

        long offset = 0;
        while (n >= Integer.MAX_VALUE){
            int bits = next(2);
            long half = n >>> 1;
            long nextn = ((bits & 2) == 0) ? half : n - half;
            if ((bits & 1) == 0) {
                offset += n - nextn;
            }
            n = nextn;
        }
        return offset + nextInt((int) n);
    }

    /**
     * The random seed. We can't use super.seed.
     */
    private long rnd;
    private static final long multiplier = 0x5DEECE66DL;
    private static final long addend = 0xBL;
    private static final long mask = (1L << 48) - 1;

    protected int next(int bits){
        rnd = (rnd * multiplier + addend) & mask;
        return (int) (rnd >>> (48 - bits));
    }

}
